package org.maxgroundstation.groundstation.view;

import java.awt.FlowLayout;
import java.awt.Graphics2D;
import java.awt.GridLayout;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;
import java.lang.reflect.Method;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Iterator;
import java.util.TimeZone;

import javax.imageio.ImageIO;
import javax.swing.DefaultComboBoxModel;
import javax.swing.ImageIcon;
import javax.swing.JButton;
import javax.swing.JCheckBox;
import javax.swing.JCheckBoxMenuItem;
import javax.swing.JComboBox;
import javax.swing.JPanel;
import javax.swing.JPopupMenu;
import javax.swing.border.TitledBorder;
import javax.swing.plaf.basic.BasicComboBoxUI;

import org.maxgroundstation.groundstation.GroundStation;
import org.maxgroundstation.groundstation.GroundStationEvent;
import org.maxgroundstation.groundstation.GroundStationListener;
import org.maxgroundstation.groundstation.comm.service.Service;
import org.maxgroundstation.groundstation.view.overlay.Overlay;
import org.maxgroundstation.groundstation.view.overlay.OverlayUtil;

/**
 * The camera control panel.
 * @author Scott Davis
 * 
 * Copyright (C) 2010  Scott Davis
 * 
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 * 
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 * 
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */
@SuppressWarnings("serial")
public class CameraControlPanel extends JPanel implements GroundStationListener {

    // Static members
    private String DEFAULT_DIR = System.getProperty("user.home") + File.separator + 
            "max_groundstation" + File.separator + "snapshots";
    
    // Data members
    private GroundStation gs;
    private DefaultComboBoxModel videoModeModel;
    private JComboBox videoModeCB;
    private DefaultComboBoxModel videoServiceModel;
    private JComboBox videoServiceCB;
    private JButton overlayButton;
    private JPopupMenu overlayMenu;
    private JCheckBox cameraLockCB;
    private JCheckBox lowBandwidthVideoCB;
    private JButton snapshotButton;
    private DateFormat dateFormatter;
    
    /**
     * Constructor
     * @param gs the ground station.
     */
    public CameraControlPanel(GroundStation gs) {
        super();
        
        // Initialize data members.
        this.gs = gs;
        dateFormatter = new SimpleDateFormat("yyyy-MM-dd'T'HH-mm-ssz");
        dateFormatter.setTimeZone(TimeZone.getTimeZone("UTC"));
        
        // Set layout.
        setLayout(new GridLayout(6, 1));
        
        // Set border.
        setBorder(new TitledBorder("Camera Configuration"));
        
        // Create video service panel.
        JPanel videoServicePanel = new JPanel(new FlowLayout(FlowLayout.CENTER, 0, 0));
        add(videoServicePanel);
        
        // Create video service combo box.
        videoServiceModel = new DefaultComboBoxModel();
        videoServiceCB = new JComboBox(videoServiceModel);
        videoServiceCB.addActionListener(new ActionListener() {
            public void actionPerformed(ActionEvent arg0) {
                String videoServiceName = (String) videoServiceModel.getSelectedItem();
                changeToNewVideoService(videoServiceName);
            }
        });
        videoServiceCB.setEnabled(false);
        videoServiceCB.setToolTipText("Select video service.");
        removeComboBoxKeyboardBinding(videoServiceCB);
        videoServicePanel.add(videoServiceCB);
        
        // Create video mode panel.
        JPanel videoModePanel = new JPanel(new FlowLayout(FlowLayout.CENTER, 0, 0));
        add(videoModePanel);
        
        // Create video mode combo box.
        videoModeModel = createVideoModeModel();
        videoModeCB = new JComboBox(videoModeModel);
        videoModeCB.setSelectedIndex(1);
        videoModeCB.addActionListener(new ActionListener() {
            public void actionPerformed(ActionEvent arg0) {
                VideoMode mode = (VideoMode) videoModeModel.getSelectedItem();
                getGroundStation().getCommandSender().videoParams(
                        mode.width, mode.height, mode.highFPS);
            }
        });
        videoModeCB.setEnabled(false);
        videoModeCB.setToolTipText("Select video resolution and rate.");
        removeComboBoxKeyboardBinding(videoModeCB);
        videoModePanel.add(videoModeCB);
        
        // Create overlay display panel.
        JPanel overlayDisplayPanel = new JPanel(new FlowLayout(FlowLayout.CENTER, 0, 0));
        add(overlayDisplayPanel);
        
        // Create overlay display button.
        overlayButton = new JButton("Overlay Display Options");
        overlayButton.addActionListener(new ActionListener() {
            public void actionPerformed(ActionEvent arg0) {
                if (overlayMenu == null) createOverlayMenu();
                overlayMenu.show(overlayButton, 0, overlayButton.getHeight());
            }
        });
        overlayButton.setEnabled(false);
        overlayButton.setToolTipText("Select which overlays to display.");
        overlayDisplayPanel.add(overlayButton);
        
        // Create camera turret drive lock check box.
        cameraLockCB = new JCheckBox("Lock Camera to Drive");
        cameraLockCB.setSelected(gs.getCameraDriveLock());
        cameraLockCB.addActionListener(new ActionListener() {
            public void actionPerformed(ActionEvent event) {
                JCheckBox cb = (JCheckBox) event.getSource();
                getGroundStation().setCameraDriveLock(cb.isSelected());
            }
        });
        cameraLockCB.setEnabled(false);
        cameraLockCB.setToolTipText("Set camera to follow direction the rover is driving.");
        add(cameraLockCB);
        
        // Create use low-bandwidth video check box.
        lowBandwidthVideoCB = new JCheckBox("Use Low Bandwidth Video");
        lowBandwidthVideoCB.setSelected(gs.useLowBandwidthVideo());
        lowBandwidthVideoCB.addActionListener(new ActionListener() {
            public void actionPerformed(ActionEvent event) {
                JCheckBox cb = (JCheckBox) event.getSource();
                getGroundStation().setUseLowBandwidthVideo(cb.isSelected());
                videoModeCB.repaint();
            }
        });
        lowBandwidthVideoCB.setEnabled(false);
        lowBandwidthVideoCB.setToolTipText("Set lower video rate for low bandwidth situation.");
        add(lowBandwidthVideoCB);
        
        // Create snapshot panel.
        JPanel snapshotPanel = new JPanel(new FlowLayout(FlowLayout.CENTER, 0, 0));
        add(snapshotPanel);
        
        // Create snapshot button.
        snapshotButton = new JButton("Take Snapshot");
        snapshotButton.addActionListener(new ActionListener() {
            public void actionPerformed(ActionEvent e) {
                saveVideoImage();
            }
        });
        snapshotButton.setEnabled(false);
        snapshotButton.setToolTipText("Take snapshot pictures of all video feeds and save them " + 
                "in max_groundstation/snapshot folder.");
        snapshotPanel.add(snapshotButton);
        
        // Add as ground station listener.
        gs.addGroundStationListener(this);
    }
    
    /**
     * Change the video service to a new name.
     * @param newVideoServiceName the new video service name.
     */
    private void changeToNewVideoService(String newVideoServiceName) {
        String oldVideoServiceName = VideoDisplayUtil.getCurrentVideoServiceName();
        if ((oldVideoServiceName != null) && (!oldVideoServiceName.equals(newVideoServiceName))) {
            // Unsubscribe to old video service.
            Service oldVideoService = gs.getServiceComm().getServiceByName(oldVideoServiceName);
            if (oldVideoService != null) {
                gs.getServiceComm().unsubscribeService(oldVideoService);
            }
        }
        
        if ((newVideoServiceName != null) && (!newVideoServiceName.equals(oldVideoServiceName))) {
            // Subscribe to new video service.
            Service newVideoService = gs.getServiceComm().getServiceByName(newVideoServiceName);
            if (newVideoService != null) {
                gs.getServiceComm().subscribeService(newVideoService);
            }
        }
        
        VideoDisplayUtil.setCurrentVideoServiceName(newVideoServiceName);
    }
    
    /**
     * Gets the ground station.
     * @return ground station.
     */
    private GroundStation getGroundStation() {
        return gs;
    }
    
    @Override
    public void groundStationUpdate(GroundStationEvent event) {
        // If services initialized from robot, check for any video services.
        if (GroundStationEvent.SERVICES_INITIALIZED.equals(event.getType())) {
            GroundStation gs = (GroundStation) event.getSource();
            Iterator<Service> i = gs.getServiceComm().getServices().iterator();
            while (i.hasNext()) {
                Service service = i.next();
                
                // Subscribe to any services with "video" in their name.
                if (service.getName().toLowerCase().indexOf("video") >= 0) {
                    videoServiceModel.addElement(service.getName());
                }
            }
            
            // Enable widgets.
            videoModeCB.setEnabled(true);
            videoServiceCB.setEnabled(true);
            overlayButton.setEnabled(true);
            cameraLockCB.setEnabled(true);
            lowBandwidthVideoCB.setEnabled(true);
            snapshotButton.setEnabled(true);
            
            // Update check boxes.
            cameraLockCB.setSelected(gs.getCameraDriveLock());
            lowBandwidthVideoCB.setSelected(gs.useLowBandwidthVideo());
        }
        else if (GroundStationEvent.SERVICES_CLOSED.equals(event.getType())) {
            // If closing services, clear video service names.
            videoServiceModel.removeAllElements();
            
            // Disable widgets.
            videoModeCB.setEnabled(false);
            videoServiceCB.setEnabled(false);
            overlayButton.setEnabled(false);
            cameraLockCB.setEnabled(false);
            lowBandwidthVideoCB.setEnabled(false);
            snapshotButton.setEnabled(false);
            
            // Set overlay menu to null.
            overlayMenu = null;
        }
    }
    
    /**
     * Creates the overlay display menu.
     */
    private void createOverlayMenu() {
        overlayMenu = new JPopupMenu("Overlay Display Options");
        
        Iterator<Overlay> i = OverlayUtil.getOverlays().iterator();
        while (i.hasNext()) {
            Overlay overlay = i.next();
            if (overlay.canDisplay()) {
                JCheckBoxMenuItem item = new JCheckBoxMenuItem(overlay.getName(), overlay.getDisplay());
                item.addActionListener(new ActionListener() {
                    public void actionPerformed(ActionEvent event) {
                        JCheckBoxMenuItem item = (JCheckBoxMenuItem) event.getSource();
                        Overlay overlay = OverlayUtil.getOverlay(item.getText());
                        if (overlay != null) {
                            overlay.setDisplay(item.isSelected());
                        }
                    }
                });
                overlayMenu.add(item);
            }
        }
    }
    
    /**
     * Saves the current video image to a jpeg file.
     */
    private void saveVideoImage() {
        
        Iterator<String> i = VideoDisplayUtil.getVideoServicesNames().iterator();
        while (i.hasNext()) {
            String videoServiceName = i.next();
            ImageIcon videoImage = VideoDisplayUtil.getCurrentVideoImage(videoServiceName);
            if (videoImage != null) {
                BufferedImage bImage = new BufferedImage(videoImage.getIconWidth(), videoImage.getIconHeight(),
                        BufferedImage.TYPE_3BYTE_BGR);
                Graphics2D g2 = bImage.createGraphics();
                g2.drawImage(videoImage.getImage(), 0, 0, null);
                g2.dispose();
                
                try {
                    File defaultDir = new File(DEFAULT_DIR);
                    if (!defaultDir.exists()) {
                        defaultDir.mkdirs();
                    }
                    String filename = dateFormatter.format(new Date()) + "_" + videoServiceName + ".jpg";
                    ImageIO.write(bImage, "jpg", new File(DEFAULT_DIR + File.separator + filename));
                    
                }
                catch (IOException e) {
                    e.printStackTrace(System.err);
                }
            }
        }
    }
    
    /**
     * Creates the video mode model.
     * @return model.
     */
    private DefaultComboBoxModel createVideoModeModel() {
        DefaultComboBoxModel model = new DefaultComboBoxModel();
        
        model.addElement(new VideoMode(160, 120, 30, 8));
        model.addElement(new VideoMode(320, 240, 30, 8));
        model.addElement(new VideoMode(640, 480, 10, 3));
        model.addElement(new VideoMode(800, 600, 7, 2));
        model.addElement(new VideoMode(1600, 1200, 1, 1));
        
        return model;
    }
    
    /**
     * Removes keyboard bindings from a JComboBox component.
     * @param cb the combo box component.
     */
    private void removeComboBoxKeyboardBinding(JComboBox cb) {
        try {
            Method uninstallKeyboardActions = BasicComboBoxUI.class.getDeclaredMethod("uninstallKeyboardActions",
                    new Class[] { });
            uninstallKeyboardActions.setAccessible(true);
            uninstallKeyboardActions.invoke(cb.getUI());
        }
        catch (Exception e) {
            e.printStackTrace(System.err);
        }
    }
    
    /**
     * Inner class for the video mode info.
     */
    private class VideoMode {
       
        // Data members.
        int width;
        int height;
        int highFPS;
        int lowFPS;
        
        /**
         * Constructor.
         * @param width the video width.
         * @param height the video height.
         * @param highFPS the high FPS rate.
         * @param lowFPS the low FPS rate.
         */
        VideoMode(int width, int height, int highFPS, int lowFPS) {
            this.width = width;
            this.height = height;
            this.highFPS = highFPS;
            this.lowFPS = lowFPS;
        }
        
        @Override
        public String toString() {
            String result = "";
            if (getGroundStation().useLowBandwidthVideo()) 
                result = "" + width + " x " + height + " at " + lowFPS + " FPS";
            else result = "" + width + " x " + height + " at " + highFPS + " FPS";
            return result;
        }
    }
}